/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hps.control;

import hps.model.DiedEvent;
import hps.model.DiedListener;
import hps.model.EmergencyExit;
import hps.model.ExitEvent;
import hps.model.FloorField;
import hps.model.IInfluence;
import hps.model.Pedestrian;
import hps.model.Point;
import hps.model.Room;
import hps.model.Statistic;
import java.awt.Color;
import java.awt.Graphics;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Hauptcontroller fuer Simulationsablauf
 *
 * @author $Author: NIKE3500 $
 * @version $Revision: 94 $
 * @date $Date: 2013-01-22 21:08:20 +0000 (Tue, 22 Jan 2013) $
 */
public class SimulationController extends Room implements DiedListener
{

    private FloorField dynamicField;
    private FloorField staticField;
    private Statistic statistic;
    private ArrayList<Point> emergencyExits;
    private int tick;

    /**
     * Erstellt einen nuen Simulationcontroller
     *
     * @param map Zu benutzende Hinderniskarte
     */
    public SimulationController(IInfluence[][] blockedFieldsMap, ArrayList<Pedestrian> pedestrians, ArrayList<IInfluence> influences, ArrayList<IInfluence> staticInfluences)
    {
        super(blockedFieldsMap.length, blockedFieldsMap[0].length);

        if (!checkMapSquare(blockedFieldsMap))
        {
            throw new IllegalArgumentException("Karte muss rechteckig sein.");
        }
        dynamicField = new FloorField(xDim, yDim);
        staticField = new FloorField(xDim, yDim);

        this.blockedFields = cloneBlockedFields(blockedFieldsMap);
        tick = 0;
        this.pedestrians = new ArrayList<Pedestrian>();
        for (Pedestrian p : pedestrians)
        {
            this.pedestrians.add((Pedestrian) p.clone());
        }
        this.influences = new ArrayList<IInfluence>();
        for (IInfluence inf : influences)
        {
            this.influences.add(inf.clone());
        }
        this.staticInfluences = new ArrayList<IInfluence>();
        for (IInfluence inf : staticInfluences)
        {
            this.staticInfluences.add(inf.clone());
        }

        checkConsistancy();

        for (IInfluence inf : staticInfluences)
        {
            inf.setInfluence(staticField);
        }

        statistic = new Statistic(xDim, yDim, pedestrians);

        Pedestrian.addListerner(this);

        //Suchen der EmergencyExits
        emergencyExits = new ArrayList<Point>();
        for (IInfluence inf : staticInfluences)
        {
            if (inf instanceof EmergencyExit)
            {
                emergencyExits.addAll(((EmergencyExit) inf).getExitPoints());
            }
        }
    }

    /**
     * Prueft ob eine Karte rechteckig ist
     *
     * @param map zu pruefende Karte
     */
    private boolean checkMapSquare(IInfluence[][] map)
    {
        int firstLen = map[0].length;
        for (IInfluence[] current : map)
        {
            if (current.length != firstLen)
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Laesst den Controller eine Iteration fortschreiten
     */
    public void iterate()
    {
        if (!simulationEnded())
        {

            tick++;

            //Pedestrians bewegen
            movePedestriansSilent();
            //Einfluesse setzen
            for (IInfluence i : influences)
            {
                i.setInfluence(dynamicField);
            }
            //Einfluesse von Pedestrians setzen
            for (Pedestrian p : pedestrians)
            {
                p.setInfluence(dynamicField);
            }
            //Einfluesse ausbreiten setzen
            spreadInfluencesSilent();

            //DAMAGEEE
            for (IInfluence inf : influences)
            {
                inf.damage(pedestrians);
            }

            //Benachrichtigen
            notifyChanges();
        }
    }

    /**
     * Bewegt alle Pedestrians und vermeided Kollisionen.
     */
    private void movePedestriansSilent()
    {
        HashMap<Point, ArrayList<Pedestrian>> pointPedestrianMap = new HashMap<Point, ArrayList<Pedestrian>>();
        for (Pedestrian p : pedestrians)
        {
            Point pnt = p.calcMove(dynamicField, staticField, blockedFields, emergencyExits);
            if (!pointPedestrianMap.containsKey(pnt))
            {
                pointPedestrianMap.put(pnt, new ArrayList<Pedestrian>());
            }
            pointPedestrianMap.get(pnt).add(p);
        }
        for (ArrayList<Pedestrian> currentList : pointPedestrianMap.values())
        {
            Pedestrian highest = currentList.get(0);
            for (Pedestrian p : currentList)
            {
                if (p.getVelocity() > highest.getVelocity())
                {
                    highest = p;
                }
            }
            blockedFields[highest.getPos().getX()][highest.getPos().getY()] = null;
            highest.move();
            blockedFields[highest.getPos().getX()][highest.getPos().getY()] = highest;
        }

        //Bitte kontrollieren Sie alle Ausgaenge und lassen Sie ihr Gepaeck zurueck
        for (Point p : emergencyExits)
        {
            if ((blockedFields[p.getX()][p.getY()] != null)
                    && (blockedFields[p.getX()][p.getY()] instanceof Pedestrian))
            {
                Pedestrian ped = (Pedestrian) blockedFields[p.getX()][p.getY()];
                ped.notifyOfRescue(new ExitEvent(this, ped));
                removePedestrianSilent(ped);
                statistic.pedestrianExit(ped, tick);
            }
        }
    }

    /**
     * Breitet alle Einfluesse aus
     */
    private void spreadInfluencesSilent()
    {
        //Momentane Einflussliste speichern
        ArrayList<IInfluence> localInfluences = (ArrayList<IInfluence>) influences.clone();
        //Liste und Map bereinigen
        while (influences.size() > 0)
        {
            removeInfluenceSilent(influences.get(0));
        }
        //neue Einfluess hinzufuegen
        for (IInfluence i : localInfluences)
        {
            ArrayList<IInfluence> tmpList = i.spread(blockedFields);
            for (IInfluence j : tmpList)
            {
                addInfluenceSilent(j);
            }
        }
    }

    /**
     * Gibt an, ob Simulation geendet hat.
     *
     * @return
     */
    public boolean simulationEnded()
    {
        return pedestrians.isEmpty();
    }

    @Override
    public Object clone()
    {
        SimulationController rtrn = new SimulationController(blockedFields, pedestrians, influences, staticInfluences);

        return rtrn;
    }

    /**
     * Ueberschreibt drawMethode von Room, um die FloorFields mitzuzeichnen
     *
     * @param g zu nutzende Instanz von Graphics
     * @param roomPosition Ursprungsposition des Raums
     * @param singleFieldSize Groesse eines einzelnen Feldes
     */
    @Override
    public void draw(Graphics g, Point roomPosition, int singleFieldSize)
    {
        g.setColor(Color.BLACK);
        dynamicField.draw(g, roomPosition, singleFieldSize);
        staticField.draw(g, roomPosition, singleFieldSize);
        super.draw(g, roomPosition, singleFieldSize);
    }

    @Override
    public void pedestrianDied(DiedEvent e)
    {
        removePedestrianSilent(e.getDeadPedestrian());
        statistic.pedestrianDied(e.getDeadPedestrian(), tick);
    }

    public void writeStat(String path) throws IOException
    {
        statistic.writeToFile(path);
    }
}
